﻿using System;
using System.Collections.Generic;
using System.Linq;
using Ewk.MachineLearning.Genetic.Programming.Program;
using Ewk.MachineLearning.Genetic.Programming.Program.Terminals;

namespace Ewk.MachineLearning.Genetic.Programming
{
    internal class TerminalNodeFactory
    {
        private static readonly Random Random = new Random(DateTime.UtcNow.Millisecond);

        private readonly List<TerminalNode> _nodes;
        private readonly List<VariableNode> _variableNodes;

        #region Constructors

        public TerminalNodeFactory(IEnumerable<Variable> variables)
            : this(variables, new List<dynamic> { 1, Math.PI, Math.E })
        {
        }

        public TerminalNodeFactory(IEnumerable<Variable> variables, IEnumerable<dynamic> constantValues)
        {
            _variableNodes = InitializeVariableNodes(variables).ToList();
            var constantNodes = InitializeConstantNodes(constantValues);

            _nodes = _variableNodes.Concat(constantNodes).ToList();
        }

        #endregion
        
        /// <summary>
        /// Gets a random <see cref="TerminalNode"/> from the predefined set of nodes.
        /// </summary>
        /// <param name="expectedOutputTypes">The types that are expected by the consumer of the <see cref="Node"/>.</param>
        /// <returns>A random <see cref="TerminalNode"/> from the predefined set of nodes.</returns>
        public TerminalNode GetRandomNode(IEnumerable<Type> expectedOutputTypes)
        {
            var suitableNodes = _nodes
                .Where(node =>
                       node.TerminalDefinition.OutputDefinition.Types
                           .Where(expectedOutputTypes.Contains)
                           .Count() != 0)
                .ToList();

            var i = Random.Next(suitableNodes.Count);

            return suitableNodes.ElementAt(i);
        }

        public IEnumerable<VariableNode> VariableNodes
        {
            get { return _variableNodes; }
        }

        private static IEnumerable<VariableNode> InitializeVariableNodes(IEnumerable<Variable> variables)
        {
            return variables
                .Select(variable =>
                        new VariableNode(variable));
        }

        private static IEnumerable<TerminalNode> InitializeConstantNodes(IEnumerable<dynamic> values)
        {
            return values
                .Select(value =>
                        new ConstantNode(
                            new Constant(value, value.GetType())
                            )
                );
        }
    }
}